﻿/*******************************************************************************
 * Gadget Utility Functions                       http://www.codeplex.com/chirpr
 *                                              Microsoft Public License (Ms-PL)
 * Authors
 *  - Charles Schmidt   2007-08  http://www.pickelhau.be
 *  - Michael Flanakin  2008-09  http://www.michaelflanakin.com
 *
 * util
 *  - animator   : positionally animates images
 *  - busyIcon   : standard busy icon
 *  - date       : date functions
 *  - error      : error functions
 *  - http       : http functions
 *  - opacity    : opacity-setting functions
 *  - scrolling  : scrolling support
 *  - string     : string functions
 *  - textarea   : functions to support textarea controls
 *  - addHandler : adds an event handler
 *
 * gadget
 *  - host       : host properties
 *  - flyout     : flyout show/hide
 *  - logging    : logging functions
 *  - properties : property getter/setter
 *  - other      : name, version, init(), load()
 ******************************************************************************/

var util = 
{
	/***************************************************************************
	 * util.animator
	 *  - add    : starts animating an image; parameters: <div>, <image url>, 
	 *             <image width>, <full width>, <image height>, <speed in ms>
	 *  - remove : stops animating an image; parameters: <index>
	 **************************************************************************/
	animator:
	{
		objects: [],
		add: function(o, s, x, w, h, speed)
		{
			var o1 = document.createElement("DIV");
			
			with (o1)
			{
				id = o.id + "_animate_0";
				style.width = x + "px";
				style.height = h + "px";
				style.overflow = "hidden";
				
				unselectable = "on";
			   
				var oImg1 = document.createElement("IMG");
				oImg1.id = o.id + "_animate_1";
				oImg1.src = s;
				oImg1.disabled = false;
				oImg1.style.position = "absolute";
				oImg1.style.top = "0px";
				oImg1.style.left = "0px";
				o1.appendChild(oImg1);
			}
			
			o.appendChild(o1);
			
			var n = -1;
			
			for (var i = 0; i < util.animator.objects.length; i++)
			{
				if (util.animator.objects[i] == null)
				{
					util.animator.objects[i] = [o, null];
					n = i;
				}
			}
			
			if (n == -1)
			{
				util.animator.objects.push([o, null]);
				n = util.animator.objects.length - 1;
			}
			
			util.animator.objects[n][1] = setTimeout("util.animator.rotate(" + n + "," + x + "," + w + "," + speed + ")", speed);
			
			return n;
		},
		remove: function(n)
		{
			if (util.animator.objects[n] != null)
			{
				var o = document.getElementById(util.animator.objects[n][0].id + "_animate_0");
				
				if (o)
				{
					o.innerHTML = "";
					
					util.animator.objects[n][0].removeChild(o);
				}
				
				clearTimeout(util.animator.objects[n][1]);
				util.animator.objects[n] = null;
			}
		},
		rotate: function(n, x, w, speed)
		{
			var o = document.getElementById(util.animator.objects[n][0].id + "_animate_1");

			if (o != null) 
			{
				var x0 = parseInt(o.style.left) - x;
				
				if (Math.abs(x0) >= w)
				{
					x0 = 0;
				}
				
				o.style.left = x0 + "px";
				
				util.animator.objects[n][1] = setTimeout("util.animator.rotate(" + n + "," + x + "," + w + "," + speed + ")", speed);
			}
		}
	},
	
	
	/***************************************************************************
	 * util.busyIcon
	 *  - show : displays the busy icon; parameters: <div>
	 *  - hide : hides the busy icon; parameters: <div>
	 **************************************************************************/
	busyIcon:
	{
		show: function(div){div.index = animator.add(div, "img/busyicon.png", 16, 288, 16, 20);},
		hide: function(div){animator.remove(div.index);}
	},
	
	
	/***************************************************************************
	 * util.codeplex
	 *  - getDefaultVersion : gets the default version for the specified project
	 **************************************************************************/
	codeplex:
	{
		getDefaultVersion: function(proj)
		{
			var http = util.http.create();
			http.open("get", "http://"+proj+".codeplex.com/Release/ProjectReleases.aspx", false);
			http.send();
			var html = http.responseText;
			var ver = new String(html.match(/title="Default Release: [0-9]+.[0-9]+(.[0-9]+(.[0-9]+)?)?"/gm));
			if (ver) { ver = ver.substring(24, ver.length-1); }
			return ver;
		}
	},
	
	
	/***************************************************************************
	 * util.date
	 *  - toLongDateString      : Mon d, yyyy
	 *  - toShortDateString     : m/d/yyyy
	 *  - toShortDateTimeString : m/d/yyyy hh:mi:ss a/pm
	 *  - toTimeString          : hh:mi:ss a/pm
	 **************************************************************************/
	date:
	{
		toLongDateString: function(date)
		{
			// Mon d, yyyy
			if (date == null) { return null; }
			var months = new Array("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
			return months[date.getMonth()] + " " + date.getDate() + ", " + date.getFullYear();
		},

		toShortDateString: function(date)
		{
			// m/d/yyyy
			if (date == null) { return null; }
			return (date.getMonth()+1) + "/" + date.getDate() + "/" + date.getFullYear();
		},

		toShortDateTimeString: function(date)
		{
			if (date == null) { return null; }
			return util.date.toShortDateString(date) + " " + util.date.toTimeString(date);
		},
		
		toTimeString: function(date)
		{
			// hh:mi:ss a/pm
			if (date == null) { return null; }
			var hour = date.getHours();
			if (hour == 0) { hour = 12; } else if (hour > 12) { hour -= 12; }
			return hour
				+ ":" + util.string.padLeft(date.getMinutes(), 0, 2) 
				+ ":" + util.string.padLeft(date.getSeconds(), 0, 2)
				+ ((date.getHours() < 12) ? " am" : " pm");
		}
	},
	
	
	/***************************************************************************
	 * util.error
	 *  - isError : determines whether the specified object is an error
	 **************************************************************************/
	error:
	{
		isError: function(e) { return (typeof(e)!="undefined" && typeof(e.description)!="undefined"); }
	},
	
	
	/***************************************************************************
	 * util.http
	 *  - create    : retrieves a new http object instance
	 *  - isSuccess : determines if the http call was successful
	 *  - send      : opens and sends a new web request; params: <get|post>, 
	 *                <url>, <username>, <password>
	 *  - get       : performs an http get; params: <url>, <user>, <password>
	 *  - post      : performs an http post; params: <url>, <user>, <password>
	 **************************************************************************/
	http:
	{
		create: function()
		{
			if (typeof(XMLHttpRequest) != 'undefined')
			{
				return new XMLHttpRequest();
			}
			try
			{
				return new ActiveXObject("Msxml2.XMLHTTP");
			}
			catch(e)
			{
				try
				{
					return new ActiveXObject("Microsoft.XMLHTTP");
				}
				catch(e)
				{}
			}
		},
		isSuccess: function(http){return (http.status==200);},
		send: function(type, url, uid, pwd)
		{
			return util.http.sendAsync(type, url, null, null, uid, pwd);
		},
		sendAsync: function(type, url, success, failed, uid, pwd)
		{
			var isAsync = (success != null);
			var http = util.http.create();
			if (util.string.isEmpty(uid) || util.string.isEmpty(pwd))
			{
				http.open(type, url, isAsync);
			}
			else
			{
				http.open(type, url, isAsync, uid, pwd);
			}
			if (isAsync)
			{
				http.onreadystatechange = function()
				{
					if (http.readyState != 4) { return; }
					if (util.http.isSuccess(http))
					{
						success(http);
					}
					else
					{
						failed(http, http.status);
					}
					http = null;
				};
			}
			http.send();
			if (!isAsync)
			{
				if (util.http.isSuccess(http)) { return http; } else { throw(http.status); }
			}
		},
		get:  function(url, uid, pwd) { return util.http.send("get",  url, uid, pwd); },
		post: function(url, uid, pwd) { return util.http.send("post", url, uid, pwd); },
		getAsync:  function(url, success, failed, uid, pwd) { return util.http.sendAsync("get", url, success, failed, uid, pwd); }
	},
	
	
	/***************************************************************************
	 * util.opacity
	 *  - set         : sets opacity; params: <node>, <opacity>
	 *  - lighten     : sets opacity to 25%; params: <node>
	 *  - lightenLess : sets opacity to 50%; params: <node>
	 *  - darken      : sets opacity to 60%; params: <node>
	 *  - darkenMore  : sets opacity to 100%; params: <node>
	 **************************************************************************/
	opacity:
	{
		set         : function(n, o) { if (n==null) { return; } n.style.filter = 'alpha(opacity='+o+')'; },
		lighten     : function(n) { util.opacity.set(n,25); },
		lightenLess : function(n) { util.opacity.set(n,50); },
		darken      : function(n) { util.opacity.set(n,60); },
		darkenMore  : function(n) { util.opacity.set(n,100); }
	},                                                   
	
	
	/***************************************************************************
	 * util.scrolling
	 *  - scroll : scrolls up/down; params: <container id>, <direction/speed>, 
	 *             <continue scrolling flag>
	 *  - start  : starts auto-scrolling; params: <container id>, <dir/speed>
	 *  - stop   : stops auto-scrolling
	 **************************************************************************/
	scrolling:
	{
		//***| properties |*****************************************************
		
		__scrollDirSpeed : 0,

		
		//***| public |*********************************************************
		
		scroll:	function(id, dirSpeed, isLoop)
		{
			// get container
			var ctr = $('#'+id);
			if (ctr == null) { alert("Container to scroll not found"); return; }
			
			// if looping and different direction/speed, stop
			if (isLoop && util.scrolling.__scrollDirSpeed != dirSpeed){return;}
			
			// scroll
			ctr.scrollTop = ctr.scrollTop + (10*dirSpeed);
			
			// loop
			if (isLoop) { setTimeout("util.scrolling.scroll('"+id+"',"+dirSpeed+",true)",50); }
		},
		start: function(id, dirSpeed)
		{
			if(util.scrolling.__scrollDirSpeed != dirSpeed)
			{
				util.scrolling.__scrollDirSpeed = dirSpeed;
				util.scrolling.scroll(id, dirSpeed, true);
			}
		},
		stop: function() { util.scrolling.__scrollDirSpeed = 0; }
	},
	
	
	/***************************************************************************
	 * util.string
	 *  - isEmpty : determines if string is null/empty
	 *  - padLeft : pads a string with characters
	 **************************************************************************/
	string:
	{
		isEmpty: function(str){return (str==null || str=='');},
		padLeft: function(value, chr, len)
		{
			var s = new String(value);
			if (s == null) { s = ""; }
			for (var i=0; i<len-s.length; i++) { s = chr + s; }
			return s;
		}
	},
	
	
	/***************************************************************************
	 * util.textarea
	 *  - colorAt80 : color to apply to the "chars left" label when at 80%
	 *  - colorAt90 : color to apply to the "chars left" label when at 90%
	 *  - register  : registers a textarea control as a managed control; 
	 *                parameters: <textarea object>, <max length>, <count label>
	 **************************************************************************/
	textarea:
	{
		//***| properties |*****************************************************
		
		colorAt80: '#cc0',
		colorAt90: '#c00',
		
		
		//***| private |********************************************************
		
		__keyPress: function()
		{
			var ta = event.srcElement;
			return (ta.value.length < ta.maxlength*1);
		},
		__keyUp: function()
		{
			var ta = event.srcElement;
			if (ta.value.length > (ta.maxlength*1))
			{
				ta.value = ta.value.substring(0, (ta.maxlength*1));
			}
			if (ta.charsLeftLabel != null)
			{
				var len = ta.value.length;
				ta.charsLeftLabel.innerText   = (ta.maxlength-len);
				if (len > (ta.maxlength*.9))
				{
					ta.charsLeftLabel.style.color = (ta.colorAt90!=null ? ta.colorAt90 : util.textarea.colorAt90);
				}
				else if (len > (ta.maxlength*.8))
				{
					ta.charsLeftLabel.style.color = (ta.colorAt80!=null ? ta.colorAt80 : util.textarea.colorAt80);
				}
				else
				{
					ta.charsLeftLabel.style.color = '';
				}
			}
		},

		
		//***| public |*********************************************************
		
		register: function(ta, max, lbl)
		{
			if (ta==null){alert('No textarea node specified');return;}
			ta.maxlength      = max;
			ta.charsLeftLabel = lbl;
			util.addHandler(ta, "onkeypress", util.textarea.__keyPress);
			util.addHandler(ta, "onkeyup",    util.textarea.__keyUp);
		}
	},
	
	
	/***************************************************************************
	 * util.version : version-handling functions
	 **************************************************************************/
	version:
	{
		isNewer: function(current, latest)
		{
			if (util.string.isEmpty(current) || util.string.isEmpty(latest)) { return false; }
			latest = latest.split(".");
			current = current.split(".");
			for (var i=0; i<4; i++)
			{
				var l = (latest.length > i) ? parseInt(latest[i]) : 0;
				var c = (current.length > i) ? parseInt(current[i]) : 0;
				if (l > c) { return true; } else if (l < c) { return false; }
			}
			return false;
		}
	},
	
	
	/***************************************************************************
	 * util.addHandler() : adds event handler to object
	 **************************************************************************/
	addHandler: function(obj, evt, fnc)
	{
		if (obj==null || evt==null || fnc==null){return;}
		if (obj.attachEvent)
		{
			obj.attachEvent(evt, fnc);
		}
		else
		{
			obj.addEventListener(evt.substring(2), fnc, false);
		}
	}
};

var gadget = null;

// if this is NOT the main window, reference parent; otherwise, create a new gadget instance
if (window.opener != null || (typeof(System) != "undefined" && document.body != System.Gadget.document.body))
{
	gadget                 = (window.opener != null) ? window.opener.gadget : System.Gadget.document.gadget;
	gadget.flyout.window   = window;
	gadget.flyout.document = document;
}
else
{
	gadget = 
	{
		name     : null,
		version  : null,
		document : null,
		

		/***************************************************************************
		 * gadget.host
		 *  - debug         : debug mode indicator
		 *  - browser       : browser host indicator
		 *  - sidebar       : sidebar host indicator
		 *  - offline       : connectivity state indicator
		 *  - setSettings() : sets the settings page
		 **************************************************************************/
		host:
		{
			debug    : false,
			browser  : (typeof(System)=="undefined"),
			sidebar  : (typeof(System)!="undefined"),
			offline  : false,
		
			setSettings: function(url) { if (gadget.host.sidebar) { System.Gadget.settingsUI = url; } }
		},
		
		
		/***************************************************************************
		 * gadget.flyout
		 *  - hide : hides the flyout
		 *  - show : shows the flyout
		 **************************************************************************/
		flyout:
		{
			window   : null,
			document : null,
			
			hide: function()
			{
				if (gadget.host.sidebar) { System.Gadget.Flyout.show = false; }
				if (gadget.flyout.window != null) { gadget.flyout.window.close(); }
			},
			show: function(page, dbg)
			{
				// append debug information, if in browser
				if (gadget.host.browser)
				{
					var hasQueryString = (document.location.search == null);
					var hasDebugInfo = (typeof(dbg) == "undefined");
					window.open(page + (!hasQueryString ? "" : document.location.search+"&"+(!hasDebugInfo?"":dbg)));
					return;
				}
				
				// close it, if it's already open (prevents crashing the gadget)
				System.Gadget.Flyout.show = false;
				
				// open flyout
				System.Gadget.Flyout.file = page;
				System.Gadget.Flyout.show = true;
			}
		},


		/***************************************************************************
		 * gadget.logging
		 *  - clear            : clears the history
		 *  - formatEntry      : formats a log entry as text
		 *  - log              : logs a message
		 *  - registerListener : registers a new listener
		 **************************************************************************/
		logging:
		{
			history     : new Array(),
			listeners   : new Array(),
			enabled     : true,
			enabledFlag : "gadget.logging.enabled",
			entryPoint  : "[STARTING...]",
			exitPoint   : "[...FINISHED]",
			checkPoint  : "[CHECKPOINT]",
			actions:
			{
				init            : 00,
				javascriptError : 01,
				logging         : 02,
				saveSettings    : 10,
				authenticate    : 20,
				getMessages     : 21,
				postMessage     : 22,
				followUser      : 23,
				            
				toString: function(a)
				{
					switch (a)
					{
						case gadget.logging.actions.init           :return "init";
						case gadget.logging.actions.javascriptError:return "javascript error";
						case gadget.logging.actions.logging        :return "logging";
						case gadget.logging.actions.saveSettings   :return "save settings";
						case gadget.logging.actions.authenticate   :return "authenticate";
						case gadget.logging.actions.getMessages    :return "get messages";
						case gadget.logging.actions.postMessage    :return "post message";
						case gadget.logging.actions.followUser     :return "follow user";
						default:return "unknown";                      
					}
				}
			},


			//***| sets a value indicating whether logging is enabled |*********

			setEnabled: function(on){ gadget.settings.set(gadget.logging.enabledFlag, (on==true)); gadget.logging.enabled = (on==true); },


			//***| handles javascript errors |**********************************

			onerror: function(msg,url,ln){util.logging.log("unknown", gadget.logging.actions.javascriptError, msg+" ("+url+" @ Ln "+ln+")", false, "");},


			//***| clears the log |*********************************************

			clear: function()
			{
				gadget.logging.history = new Array();
				gadget.logging.log("util", "gadget.logging.clear", null, gadget.logging.actions.logging, "cleared log", true, "log empty");
			},


			//***| logs a new message |*****************************************

			log: function(p, f, u, a, d, s, o)
			{
				// if disabled, return
				if (!gadget.logging.enabled) { return; }
				
				// append to history
				var item = gadget.logging.createEntry(p,f,u,a,d,s,o);
				gadget.logging.history[gadget.logging.history.length] = item;
				
				// call each listener
				for (var i=0; i<gadget.logging.listeners.length; i++)
				{
					try { gadget.logging.listeners[i](gadget.logging.formatEntry(item)); } catch (e) { /* log exception */ }
				}
				
				// condense multiple entries into one check-point
				gadget.logging.createCheckPoint(item);
			},


			//***| creates a new log entry |************************************

			createEntry: function(p,f,u,a,d,s,o)
			{
				var user = (util.string.isEmpty(u) ? "anon" : u);
				var isSuccessful = (s == null || s == true);
				return { page:p, func:f, user:user, date:new Date(), action:a, description:d, successful:isSuccessful, outcome:o };
			},


			//***| condenses multiple log entries into 1 checkpoint |***********

			createCheckPoint: function(item)
			{
				// only condense if this is an exit point
				if (item.description != gadget.logging.exitPoint) { return; }
				
				// # log entries to review before giving up -- help avoid major performance degredation
				var threshold = 25;
				var logError = function(d) { gadget.logging.log("util", "gadget.logging.createCheckPoint", item.user, gadget.logging.actions.logging, d, false, ""); };
				
				// find entry and exit points
				var entry = -1;
				var exit = -1;
				for (var i=gadget.logging.history.length-1; i>=0; i--)
				{
					var logItem = gadget.logging.history[i];
					if (!logItem.successful) { return; }
					else if (exit == -1 && logItem == item) { exit = i; }
					else if (exit == -1 && i < gadget.logging.history.length - threshold) { logError("hit threshold looking for exit point"); return; }
					else if (logItem.page == item.page && logItem.func == item.func && logItem.user == item.user && logItem.action == item.action && (logItem.description == gadget.logging.entryPoint /*|| logItem.description == gadget.logging.checkPoint*/)) { entry = i; break; }
					else if (entry == -1 && i < exit - threshold) { logError("hit threshold looking for entry point"); return; }
				}
				
				// get pre-entry, add check-point entry, append any new entries -- entries can be added while this logic is processed
				var newLog = gadget.logging.history.slice(0, entry);
				newLog[newLog.length] = gadget.logging.createEntry(item.page, item.func, item.user, item.action, gadget.logging.checkPoint, true, item.outcome);
				for (var i=exit+1; i<gadget.logging.history.length; i++)
				{
					newLog[newLog.length] = gadget.logging.history[i];
				}
				gadget.logging.history = newLog;
			},


			//***| converts a log item to a string |****************************

			formatEntry: function(item)
			{
				return "[{0} @ {1} » {4}{6} / {2}::{3}]  {5}{7}"
					.replace("{0}", (util.string.isEmpty(item.user) ? "anon" : item.user))
					.replace("{1}", util.date.toShortDateTimeString(item.date))
					.replace("{2}", item.page)
					.replace("{3}", item.func)
					.replace("{4}", gadget.logging.actions.toString(item.action))
					.replace("{5}", item.description)
					.replace("{6}", (item.successful ? "¹" : "º"))
					.replace("{7}", (util.string.isEmpty(item.outcome) ? "" : " »» " + item.outcome));
			},               


			//***| registers a new listener |***********************************

			registerListener: function(listener)
			{
				if (listener == null || typeof(listener) == "undefined") { return; }
				gadget.logging.listeners[gadget.logging.listeners.length] = listener;
				
				// add existing messages to log
				for (var i=0; i<gadget.logging.history.length; i++)
				{
					try { listener(gadget.logging.formatEntry(gadget.logging.history[i])); } catch (e) { /* log exception */ }
				}
			}
		},
		
		
		/***************************************************************************
		 * gadget.settings
		 *  - get  : gets a property from the data store; parameters: <property>
		 *  - set  : sets a property in the data store; parameters: <prop>, <value>
		 **************************************************************************/
		settings:
		{
			getInt  : function(p,d)
			{
				var raw = gadget.settings.get(p);
				var value = (util.string.isEmpty(raw) || isNaN(raw)) ? d : parseInt(raw);
				return (isNaN(value)) ? -1 : value;
			},
			getBool : function(p,d)
			{
				var raw = gadget.settings.get(p);
				var value = (util.string.isEmpty(raw)) ? d : parseInt(raw);
				var value = gadget.settings.get(p);
				return (((!util.string.isEmpty(value) && value.toLowerCase() == "true") || value) == true);
			},
			get: function(p,d)
			{
				// if in browser, get querystring value
				if (gadget.host.browser)
				{
					// get from querystring
					var q = document.location.search;
					if (q == null || q.length == 0) { return; }
					q = q.substring(1).split("&");
					for (var i=0; i<q.length; i++)
					{
						if (q[i].indexOf(p+'=') == 0)
						{
							var qsvalue = q[i].substring((p + "=").length);
							return (util.string.isEmpty(qsvalue)) ? d : qsvalue;
						}
					}
					return d;
				}
				
				// read from sidebar settings
				var sbvalue = System.Gadget.Settings.readString(p);
				return (util.string.isEmpty(sbvalue)) ? d : sbvalue;
			},
			set: function(p,v)
			{
				if (gadget.host.browser) { return; }
				System.Gadget.Settings.writeString(p,v);
			}
		},
		
		
		/***************************************************************************
		 * gadget.init() : initializes the gadget
		 **************************************************************************/
		init: function()
		{
			// DEBUG: gadget.logging.registerListener(alert);

			// attach error handler
			//onerror = gadget.logging.onerror;
			
			// log
			gadget.logging.log("util", "gadget.init", null, gadget.logging.actions.init, gadget.logging.entryPoint);
			
			// init
			gadget.name         = (gadget.host.browser) ? "Debugger" : System.Gadget.name;
			gadget.version      = (gadget.host.browser) ? "0.0" : System.Gadget.version;
			gadget.document     = document;
			gadget.host.offline = (gadget.settings.getBool("offline"));
			gadget.logging.enabled = gadget.settings.getBool(gadget.logging.enabledFlag, false);
			
			// log
			gadget.logging.log("util", "gadget.init", null, gadget.logging.actions.init, gadget.logging.exitPoint, true, 
				gadget.name + " v" + gadget.version + (gadget.host.offline ? " (offline)" : ""));
		}
	};
	gadget.init();
	if (gadget.host.sidebar) { document.gadget = gadget; }
}
